package fr.upmc.alasca.centreCalcul.interne.machinePhysique.coeur;

import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

import fr.upmc.alasca.centreCalcul.interne.machinePhysique.coeur.interfaces.CoeurClientI;
import fr.upmc.alasca.centreCalcul.interne.machinePhysique.coeur.interfaces.CoeurOperateurI;
import fr.upmc.alasca.centreCalcul.interne.machinePhysique.coeur.ports.CoeurClientInboundPort;
import fr.upmc.alasca.centreCalcul.interne.machinePhysique.coeur.ports.CoeurOperateurInboundPort;
import fr.upmc.alasca.centreCalcul.interne.machinePhysique.machineVirtuelle.connectors.MachineVirtuelleCoeurConnector;
import fr.upmc.alasca.centreCalcul.interne.machinePhysique.machineVirtuelle.interfaces.MachineVirtuelleCoeurI;
import fr.upmc.alasca.centreCalcul.interne.machinePhysique.machineVirtuelle.ports.MachineVirtuelleCoeurOutboundPort;
import fr.upmc.alasca.centreCalcul.objets.EtatCoeur;
import fr.upmc.alasca.centreCalcul.objets.Requete;
import fr.upmc.alasca.centreCalcul.outils.LogMessage;
import fr.upmc.components.AbstractComponent;


public	class	Coeur
extends			AbstractComponent
implements		CoeurClientI, CoeurOperateurI
{
	/* ------------------------------------------------------------- ATTRIBUT */
	protected	String								uri;
	protected	EtatCoeur							etat = EtatCoeur.disponible;
	protected	float								frequence = (float) 2.0;
	protected	final float							frequenceMax;
	protected	final float							frequenceMin;

	protected	String								uriApplication = null;
	protected	String								uriMachineVirtuelle;

	protected	boolean								requeteEncours = false;
	protected	Requete								requete;

	protected	boolean								reservationFrequenceEnCours = false;
	protected	float								frequenceReservee = 0;

	protected	CoeurClientInboundPort				coeurClientIPort;
	protected	CoeurOperateurInboundPort			coeurOperateurIPort;

	protected	MachineVirtuelleCoeurOutboundPort	machineVirtuelleCoeurOPort;
	protected	Object								mvCoeur_OPort_verrou = new Object();
	protected	boolean								desallouer = false;

	protected	Future<?>							nextEndServicingTaskFuture;




	/* --------------------------------------------------------- CONSTRUCTEUR */
	public Coeur
	(
		String	p_uri,
		float	p_frequenceMax,
		float	p_frequenceMin
	) throws	Exception
	{
		super(true,true);

		uri = p_uri;
		frequenceMax = p_frequenceMax;
		frequenceMin = p_frequenceMin;
		
		LogMessage.printMessage("Coeur", uri,
				"Création du coeur");
		
		this.addOfferedInterface(CoeurOperateurI.class);
		this.addOfferedInterface(CoeurClientI.class);

		this.addRequiredInterface(MachineVirtuelleCoeurI.class);

		this.coeurOperateurIPort = new CoeurOperateurInboundPort(p_uri + "_Operateur", this);
		this.addPort(this.coeurOperateurIPort);
		this.coeurOperateurIPort.localPublishPort();

		this.coeurClientIPort = new CoeurClientInboundPort(p_uri + "_Client", this);
		this.addPort(this.coeurClientIPort);
		this.coeurClientIPort.localPublishPort();
	}




	/* ------------------------------------------------------ CoeurOperateurI */
	@Override
	public	EtatCoeur			getEtat
	() throws	Exception
	{
		return etat;
	}
	
	
	@Override
	public	String				getUriApplication
	() throws	Exception
	{
		return uriApplication;
	}
	
	
	@Override
	public	float				getFrequence
	() throws	Exception
	{
		return frequence;
	}
	
	
	@Override
	public	boolean				aReservationFrequenceEnCours
	() throws	Exception
	{
		return reservationFrequenceEnCours;
	}
	
	
	@Override
	public	float				getFrequenceReservee
	() throws	Exception
	{
		return frequenceReservee;
	}
	
	
	@Override
	public	void				reserverCoeur
	(
		String	p_uriApplication
	) throws	Exception
	{
		LogMessage.printMessage("Coeur", uri,
				"Etat changé (" + etat + " => " + EtatCoeur.reserve + ")");
		
		etat = EtatCoeur.reserve;
		uriApplication = p_uriApplication;
	}


	@Override
	public	void				allouerCoeur
	(
		String	p_uriMachineVirtuelle
	) throws	Exception
	{
		LogMessage.printMessage("Coeur", uri,
				"Etat changé (" + etat + " => " + EtatCoeur.utilise + ")");
		
		uriMachineVirtuelle = p_uriMachineVirtuelle;
		etat = EtatCoeur.utilise;
		
		synchronized (mvCoeur_OPort_verrou) {
			// Connexion à la machine virtuelle
			machineVirtuelleCoeurOPort =
					new MachineVirtuelleCoeurOutboundPort(uri + "_CoeurMvco", this);
			this.addPort(machineVirtuelleCoeurOPort);
			machineVirtuelleCoeurOPort.localPublishPort();
			
			machineVirtuelleCoeurOPort.doConnection(uriMachineVirtuelle + "_Coeur",
					MachineVirtuelleCoeurConnector.class.getCanonicalName());
			
			machineVirtuelleCoeurOPort.attributionNouveauCoeur(uri);
		}
	}


	@Override
	public	void				desallouerCoeur
	() throws	Exception
	{
		LogMessage.printMessage("Coeur", uri,
				"Etat changé (" + etat + " => " + EtatCoeur.disponible + ")");
		
		etat = EtatCoeur.disponible;
		
		synchronized (mvCoeur_OPort_verrou) {
			desallouer = true;
			
			// On enlève la réservation de fréquence en lui attribuant cette fréquence
			if (reservationFrequenceEnCours) {
				frequence = frequenceReservee;
				reservationFrequenceEnCours = false;
			}
			
			etat = EtatCoeur.disponible;
			uriMachineVirtuelle = "";
			uriApplication = null;
			
			// Pour les coeurs UTILISES et pas les coeurs RESERVES
			if (machineVirtuelleCoeurOPort != null) {
				machineVirtuelleCoeurOPort.retraitCoeur(uri);
				machineVirtuelleCoeurOPort.doDisconnection();
				machineVirtuelleCoeurOPort = null;
			}
		}
	}
	

	@Override
	public	synchronized  float	reserverFrequence
	(
		float	p_frequence
	) throws	Exception
	{
		reservationFrequenceEnCours = true;
		frequenceReservee = p_frequence;
		
		return frequenceReservee;
	}

	
	@Override
 	public	void				changerFrequence
 	() throws	Exception
	{
		// TODO sauvegarder la requete en cours pour pouvoir faire le calcul du temps restant au moment du changement de la fréquence.
		if(frequenceReservee >= frequenceMin && frequenceReservee <= frequenceMax) {
			if(requete != null) {
				long tempsExecutionInitial = calculerTempsExecution(requete.getNbInstructions(), frequence);
				long tempsPasse = System.currentTimeMillis() - requete.getHeureDebutTraitement();
				
				int pourcentageAvancement = (int) (((double) tempsPasse / (double) tempsExecutionInitial) * 100);
				int pourcentageRestant = 100 - pourcentageAvancement;
				
				long nbInstructionsRestantes = (requete.getNbInstructions() / 100L) * (long) pourcentageRestant;
				long tempsExecutionRestant = calculerTempsExecution(nbInstructionsRestantes, frequenceReservee);
				
				this.nextEndServicingTaskFuture.cancel(true);
				
				final Coeur c = (Coeur) this;
				this.nextEndServicingTaskFuture =
					this.scheduleTask(
						new ComponentTask() {
							@Override
							public void run() {
								try {
									c.finRequeteEvent(requete) ;
								} catch (Exception e) {
									e.printStackTrace() ;
								}
							}
						},
						tempsExecutionRestant, TimeUnit.MILLISECONDS);
			}
			
			//TODO a synchroniser
			frequence = frequenceReservee;
			this.reservationFrequenceEnCours = false;
		}
	}
	

	@Override
	public	void				dereserverCoeur
	() throws	Exception
	{
		etat = EtatCoeur.disponible;
	}
	/* ------------------------------------------------------ CoeurOperateurI */
	
 	
 	private	long	calculerTempsExecution
 	(
 		long	p_nbInstructions,
 		float	p_frequence
 	) {
 		return (p_nbInstructions / (long) (1000000000L * p_frequence)) * 1000;
 	}


	public	void	executerRequete
	(
		final	Requete	p_requete
	) throws Exception
	{
		p_requete.setHeureDebutTraitement((System.currentTimeMillis()));
		this.requete = p_requete;

		final Coeur c = (Coeur) this;
		
		long tempsExecution = calculerTempsExecution(p_requete.getNbInstructions(), frequence);
		this.nextEndServicingTaskFuture =
				this.scheduleTask(
						new ComponentTask() {
							@Override
							public void run() {
								try {
									c.finRequeteEvent(p_requete) ;
								} catch (Exception e) {
									e.printStackTrace() ;
								}
							}},
							tempsExecution, TimeUnit.MILLISECONDS) ;
	}

	
	/**
	 * Permet de rappeler la machine virtuelle pour lui signaler la fin de notre traitement
	 * @param p_requete
	 * @throws Exception
	 */
	private	void	finRequeteEvent
	(
		Requete	p_requete
	) throws	Exception
	{
		synchronized(mvCoeur_OPort_verrou) {
			if(machineVirtuelleCoeurOPort != null)
				this.machineVirtuelleCoeurOPort.finExecutionRequete(this.uri, p_requete, p_requete.getTempsTraitement(), !this.desallouer);
		}
	}
}
